<?php 

    /**
     * This file is part of the Intelligencer package.
     * For the full copyright and license, please view the LICENSE
     * file that was distributed with this source code.
     * 
     * @author      drapeko <roman.drapeko@gmail.com>
     * @copyright   Roman Drapeko <roman.drapeko@gmail.com>, 2009 - present
     */

    /**
     * Rules Intelligencer is used when you do not know the exact file locations of your classes. 
     * It will search for the classes (using Scanner) in the specified files/directories according to
     * the istructions (Rules).
     * 
     * @author drapeko
     */
    class itRulesIntelligencer extends itIntelligencer {
        
        protected $rules = null;
        protected $scanner = null;
        protected $classRulesCache = null;
        protected $simpleRulesCache = null;
        protected $aggressiveCaching = false;
        
        /**
         * Constructor. Accepts the Intelligencer name.
         * @param string $name
         */
        public function __construct($name = null) {
            $this->classRulesCache = new itLocalCache();
            $this->simpleRulesCache = new itLocalCache();
            parent::__construct($name);
        }
        
        /**
         * Sets the Intelligencer Scanner that will be scanning files to find the classes/interfaces.
         * @param itScanner $scanner
         * @return itIntelligencer itself
         */
        public function setScanner(itScanner $scanner) {
            $this->scanner = $scanner;
            return $this;
        }
        
        /**
         * Returns the scanner. If scanner has not been set explicitly the new itScanner 
         * instance will be created and returned.
         * @return itScanner
         */
        public function getScanner() {
            if ($this->scanner == null) {
                $this->scanner = new itScanner();
            }
            return $this->scanner;
        }
        
        /**
         * Sets the Intelligencer Rules. Returns itself.
         * 
         * Current Intelligencer knows how to work with itClassRules, itClassRulesConvertible and
         * itSimpleRulesConvertible. All other rules will be simply ignored.
         * 
         * @param itRules $rules
         * @return itIntelligencer itself
         */
        public function setRules(itRules $rules) {
            $this->rules = $rules;
            $rules->setIntelligencer($this);
            return $this;
        }
        
        /**
         * Sets the Intelligencer Rules. Returns the rules you just set.
         * 
         * Current Intelligencer knows how to work with itClassRules, itClassRulesConvertible and
         * itSimpleRulesConvertible. All other rules will be simply ignored.
         * 
         * @param itRules $rules
         * @return itRules
         */
        public function setRulesAndCreate(itRules $rules) {
            $this->setRules($rules);
            return $rules;
        }
        
        /**
         * Returns the rules.
         * @return itRules
         */
        public function getRules() {
            return $this->rules;
        }
        
        /**
         * Sets the cache that is used for caching classes and their locations.
         * @param itCache $classRulesCache
         * @return itIntelligencer itself
         */
        public function setCache(itCache $classRulesCache = null) {    
            $this->classRulesCache = $classRulesCache;
            return $this;
        }
        
        /**
         * Sets the file that is used for storing the files/dir tree in the cache.
         * @param itCache $classRulesCache
         * @return itIntelligencer itself
         */
        public function setFilesCache(itCache $simpleRulesCache = null) {
            $this->simpleRulesCache = $simpleRulesCache;
            return $this;
        }

        /**
         * Sets the cache to be aggressive or non-aggressive. 
         * The aggressive cache means that if the cache exists and the class is not 
         * found in it Intelligencer won't invalidate the cache and won't search on the disk.
         * 
         * @param boolean $aggressiveCaching true
         * @return itIntelligencer itself
         */
        public function setAggressiveCache($aggressiveCaching = true) {
            $this->aggressiveCaching = $aggressiveCaching;
            return $this;
        }
        
        /**
         * Returns whether the cache is aggressive
         * @return boolean
         */
        public function isAggressiveCache() {
            return $this->aggressiveCaching;
        }
        
        /**
         * Returns the class cache
         * @return itCache
         */
        public function getCache() {
            return $this->classRulesCache;
        }
        
        /**
         * Returns the files cache
         * @return itCache
         */
        public function getFilesCache() {
            return $this->simpleRulesCache;
        }
        
        protected function getCacheKeyPrefix() {
            $name = $this->getName();
            if ($name == null) {
                $name = 'IntelligencerDoesNotHaveName';
            }
            
            return $name;
        }
        
        protected function isClassRulesCacheVirgin() {
            return ($this->classRulesCache != null && $this->classRulesCache->virgin());
        }
        
        protected function getClassRulesCacheKey() {
            return $this->getCacheKeyPrefix().'-class';
        }
        
        protected function getSimpleRulesCacheKey() {
            return $this->getCacheKeyPrefix().'-file';
        }
        
        protected function saveClassRulesCache(itClassRules $classRules) {
            if ($this->classRulesCache != null) {
                $this->classRulesCache->set($this->getClassRulesCacheKey(), $classRules);
            }
        }
        
        protected function saveSimpleRulesCache(itSimpleRules $simpleRules) {
            if ($this->simpleRulesCache != null) {
               $this->simpleRulesCache->set($this->getSimpleRulesCacheKey(), $simpleRules);
            }
        }
        
        protected function getCachedClassRules() {
            if ($this->classRulesCache == null) {
                return null;
            }
            return $this->classRulesCache->get($this->getClassRulesCacheKey());
        }
        
        protected function getCachedSimpleRules() {
            if ($this->simpleRulesCache == null) {
                return null;
            }
            return $this->simpleRulesCache->get($this->getSimpleRulesCacheKey());
        }
        
        /**
         * Ask Intelligencer to load the class according to all the instructions (cache, rules etc). 
         * This function is widely used by the itManager.
         * @param $className
         * @return boolean
         */
        public function load($className) {
            list($namespace, $class) = itUtil::getClassAndNS($className);
            
            if ($this->rules == null) {
                throw new itNullRulesException('Rules are not set');
            }
            
            if ($this->rules instanceof itClassRules) {
                return $this->loadFromClassRules($className, $this->rules);
            } elseif ($this->rules instanceof itClassRulesConvertibleI) {
                return $this->loadFromClassRulesConvertible($className, $this->rules);
            } elseif ($this->rules instanceof itSimpleRulesConvertibleI) {
                return $this->loadFromSimpleRulesConvertible($className, $this->rules);
            }
            return false;
        }
        
        protected function loadFromSimpleRulesConvertible($className, itSimpleRulesConvertibleI $rules) {
            $loaded = false;
            if ($this->classRulesCache != null) {
                $loaded = $this->loadFromCachedClassRules($className);
                if ($this->aggressiveCaching && !$this->isClassRulesCacheVirgin()) {
                    return $loaded;
                }
            }
            
            if (!$loaded) {
                $simpleRules = $this->getCachedSimpleRules();
                if ($simpleRules == null) {
                    $simpleRules = $rules->toSimpleRules();
                    $this->saveSimpleRulesCache($simpleRules);
                }
   
                $loaded = $this->loadFromClassRulesConvertible($className, $simpleRules);
            }
            
            return $loaded;
        }
        
        protected function loadFromClassRulesConvertible($className, itClassRulesConvertibleI $rules) {
            $loaded = false;
            if ($this->classRulesCache != null) {
                $loaded = $this->loadFromCachedClassRules($className);
                if ($this->aggressiveCaching && !$this->isClassRulesCacheVirgin()) {
                    return $loaded;
                }
            }
            
            if (!$loaded) {
                $classRules = $rules->toClassRules($this->getScanner());
                $this->saveClassRulesCache($classRules);
                $loaded = $this->loadFromClassRules($className, $classRules);
            }
            
            return $loaded;
        }
        
        protected function loadFromCachedClassRules($className) {
            $classRules = $this->getCachedClassRules();
            $loaded = false;
            if ($classRules != null) {
                $loaded = $this->loadFromClassRules($className, $classRules);
            }
            return $loaded;
        }
        
        protected function loadFromClassRules($className, itClassRules $rules) {
             if ($rules != null && $rules->hasClass($className)) {
                $file = $rules->getClass($className);
                if (is_readable($file) && is_file($file)) {
                    include_once($file);
                    if (class_exists($className, false) || interface_exists($className, false)) {
                        return true;
                    }
                }
            }
            return false;
        }
    }

?>